﻿using SD.Infrastructure.Repository.EntityFrameworkCore;
using SlamDunk.MES.Domain.Entities.ExecutionContext;
using SlamDunk.MES.Domain.Entities.ResourceContext;
using SlamDunk.MES.Domain.Entities.StockContext;
using SlamDunk.MES.Domain.IRepositories.Interfaces.ExecutionContext;
using SlamDunk.MES.ReportView.Models;
using SlamDunk.ValueObjects.Enums;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;

namespace SlamDunk.MES.Repository.Implements.ExecutionContext
{
    /// <summary>
    /// 工单实际物料仓储实现
    /// </summary>
    public class WorkOrderActualMaterialRepository : EFEntityRepositoryProvider<WorkOrderActualMaterial>, IWorkOrderActualMaterialRepository
    {
        #region # 获取工单实际物料列表 —— ICollection<WorkOrderActualMaterial> Find(Guid workOrderId)
        /// <summary>
        /// 获取工单实际物料列表
        /// </summary>
        /// <param name="workOrderId">工单Id</param>
        /// <returns>工单实际物料列表</returns>
        public ICollection<WorkOrderActualMaterial> Find(Guid workOrderId)
        {
            IQueryable<WorkOrderActualMaterial> actualMaterials = base.Find(x => x.WorkOrder.Id == workOrderId);

            return actualMaterials.ToList();
        }
        #endregion

        #region # 获取作业计划物料概要列表 —— ICollection<MaterialSummaryInfo> GetMaterialSummariesByExecutionPlan(...
        /// <summary>
        /// 获取作业计划物料概要列表
        /// </summary>
        /// <param name="executionPlanId">作业计划Id</param>
        /// <param name="directionType">物料输入/输出类型</param>
        /// <returns>物料概要列表</returns>
        public ICollection<MaterialSummaryInfo> GetMaterialSummariesByExecutionPlan(Guid executionPlanId, MaterialDirectionType directionType)
        {
            Expression<Func<WorkOrderActualMaterial, bool>> condition =
                x =>
                    (x.WorkOrder.ExecutionPlanId == executionPlanId) &&
                    (x.DirectionType == directionType);

            IQueryable<WorkOrderActualMaterial> actualMaterials = base.Find(condition);
            var actualMaterialGroups = actualMaterials.GroupBy(x => new
            {
                x.MaterialId,
                x.MaterialLotId
            }).Select(x => new
            {
                x.Key,
                Quantity = x.Any(y => y.Quantity.HasValue)
                    ? x.Where(y => y.Quantity.HasValue).Sum(y => y.Quantity.Value)
                    : 0
            });

            IQueryable<Material> materials = this._dbContext.Set<Material>();
            IQueryable<MaterialLot> materialLots = this._dbContext.Set<MaterialLot>();
            IQueryable<MaterialSummaryInfo> materialSummaries =
                from actualMaterialGroup in actualMaterialGroups
                let material = materials.FirstOrDefault(x => x.Id == actualMaterialGroup.Key.MaterialId)
                let materialLot = actualMaterialGroup.Key.MaterialLotId.HasValue
                    ? materialLots.FirstOrDefault(x => x.Id == actualMaterialGroup.Key.MaterialLotId.Value)
                    : null
                let quantity = actualMaterialGroup.Quantity
                select new MaterialSummaryInfo
                {
                    MaterialId = material.Id,
                    MaterialNo = material.Number,
                    MaterialName = material.Name,
                    MaterialLotId = materialLot == null ? (Guid?)null : materialLot.Id,
                    SupplierId = materialLot == null ? (Guid?)null : materialLot.SupplierId,
                    SupplierNo = materialLot == null ? null : materialLot.SupplierNo,
                    SupplierName = materialLot == null ? null : materialLot.SupplierName,
                    ProductionBatchNo = materialLot == null ? null : materialLot.ProductionBatchNo,
                    ProducedDate = materialLot == null ? (DateTime?)null : materialLot.ProducedDate,
                    GuaranteePeriodTicks = materialLot == null ? (long?)null : materialLot.GuaranteePeriodTicks,
                    ExpiredDate = materialLot == null ? (DateTime?)null : materialLot.ExpiredDate,
                    ReceptionDate = materialLot == null ? (DateTime?)null : materialLot.ReceptionDate,
                    Specification = materialLot == null ? null : materialLot.Specification,
                    MaterialSourceType = materialLot == null ? (MaterialSourceType?)null : materialLot.MaterialSourceType,
                    Quantity = quantity,
                    QuantityUnitNo = material.StandardUnitNo
                };

            return materialSummaries.ToList();
        }
        #endregion

        #region # 获取物料投入周期概要字典 —— {DateTime, MaterialQuantityInfo[]} GetMaterialInputPeriodicSummaries(...
        /// <summary>
        /// 获取物料投入周期概要字典
        /// </summary>
        /// <param name="startTime">开始时间</param>
        /// <param name="endTime">结束时间</param>
        /// <returns>物料投入周期概要字典</returns>
        /// <remarks>
        /// 键：日期，值：物料数量列表
        /// </remarks>
        public IDictionary<DateTime, MaterialQuantityInfo[]> GetMaterialInputPeriodicSummaries(DateTime startTime, DateTime endTime)
        {
            Expression<Func<WorkOrderActualMaterial, bool>> condition =
                x =>
                    x.DirectionType == MaterialDirectionType.Input &&
                    x.AddedTime >= startTime &&
                    x.AddedTime <= endTime;

            IQueryable<WorkOrderActualMaterial> actualMaterialsQueryable = base.Find(condition);
            IQueryable<Guid> materialIds = actualMaterialsQueryable.Select(x => x.MaterialId).Distinct();
            IList<WorkOrderActualMaterial> actualMaterials = actualMaterialsQueryable.ToList();

            IDictionary<Guid, Material> materials =
                base._dbContext.Set<Material>()
                    .Where(x => materialIds.Contains(x.Id))
                    .ToDictionary(x => x.Id, x => x);

            IDictionary<DateTime, MaterialQuantityInfo[]> dictionary = new Dictionary<DateTime, MaterialQuantityInfo[]>();

            var datedActualMaterials =
                from actualMaterial in actualMaterials
                select new
                {
                    ActualMaterial = actualMaterial,
                    Date = actualMaterial.AddedTime.Date
                };
            var actualMaterialDatedGroups = datedActualMaterials.AsEnumerable().GroupBy(x => x.Date);
            foreach (var actualMaterialDatedGroup in actualMaterialDatedGroups)
            {
                var actualMateriaGroups = actualMaterialDatedGroup.GroupBy(x => x.ActualMaterial.MaterialId);

                IList<MaterialQuantityInfo> materialQuantities = new List<MaterialQuantityInfo>();
                foreach (var actualMateriaGroup in actualMateriaGroups)
                {
                    Material material = materials[actualMateriaGroup.Key];
                    decimal quantity = actualMateriaGroup.Any()
                        ? actualMateriaGroup.Where(x => x.ActualMaterial.Quantity.HasValue).Sum(x => x.ActualMaterial.Quantity.Value)
                        : 0;
                    MaterialQuantityInfo materialQuantityInfo = new MaterialQuantityInfo
                    {
                        MaterialId = material.Id,
                        MaterialNo = material.Number,
                        MaterialName = material.Name,
                        Quantity = quantity,
                        QuantityUnitNo = material.StandardUnitNo
                    };
                    materialQuantities.Add(materialQuantityInfo);
                }

                dictionary.Add(actualMaterialDatedGroup.Key, materialQuantities.ToArray());
            }

            return dictionary;
        }
        #endregion
    }
}
